const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
class MyPromise {
  constructor(executor) {
    try {
      executor(this.resolve.bind(this), this.reject.bind(this));
    } catch (err) {
      this.reject(err);
    }
  }
  state = PENDING;
  value = null;
  reason = null;
  onFulfilledCallbacks = [];
  onRejectedCallbacks = [];

  resolve(value) {
    if (this.state === PENDING) {
      if (value instanceof MyPromise) {
        value.then(
          (v) => {
            this.state = FULFILLED;
            this.value = v;
            this.onFulfilledCallbacks.forEach((cb) => cb(value));
          },
          (reason) => {
            this.state = REJECTED;
            this.reason = reason;
            this.onRejectedCallbacks.forEach((cb) => cb(reason));
          }
        );
      } else {
        this.state = FULFILLED;
        this.value = value;
        this.onFulfilledCallbacks.forEach((cb) => cb(value));
      }
    }
  }

  reject(reason) {
    if (this.state === PENDING) {
      this.state = REJECTED;
      this.reason = reason;
      this.onRejectedCallbacks.forEach((cb) => cb(reason));
    }
  }

  then(
    onFulfilled = (v) => v,
    onRejected = (r) => {
      throw r;
    }
  ) {
    const promise2 = new MyPromise((resolve, reject) => {
      if (this.state === FULFILLED) {
        queueMicrotask(() => {
          try {
            const x = onFulfilled(this.value);
            resolvePromise(promise2, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      } else if (this.state === REJECTED) {
        queueMicrotask(() => {
          try {
            const x = onRejected(this.reason);
            resolvePromise(promise2, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      } else {
        this.onFulfilledCallbacks.push((value) => {
          queueMicrotask(() => {
            try {
              const x = onFulfilled(value);
              resolvePromise(promise2, x, resolve, reject);
            } catch (err) {
              reject(err);
            }
          });
        });
        this.onRejectedCallbacks.push((reason) => {
          queueMicrotask(() => {
            try {
              const x = onRejected(reason);
              resolvePromise(promise2, x, resolve, reject);
            } catch (err) {
              reject(err);
            }
          });
        });
      }
    });
    return promise2;
  }

  catch(onRejected) {
    return this.then(undefined, onRejected);
  }

  // 不管finally之前的promise是成功还是失败，只要finally中的promise成功了，就用上一个promise的结果作为返回
  // 只要finally中的promise失败了，就使用自己的 promise 结果作为返回
  finally(callback) {
    return this.then(
      (v) =>
        MyPromise.resolve(callback()).then(() => {
          return v;
        }),
      (r) =>
        MyPromise.reject(callback()).then(() => {
          throw r;
        })
    );
  }

  static resolve(value) {
    if (value instanceof MyPromise) {
      return value;
    }
    return new MyPromise((resolve) => {
      resolve(value);
    });
  }

  static reject(reason) {
    return new MyPromise((resolve, reject) => {
      reject(reason);
    });
  }

  static all(promiseArr) {
    let index = 0;
    const result = [];

    return new MyPromise((resolve, reject) => {
      promiseArr.forEach((p, i) => {
        MyPromise.resolve(p).then(
          (val) => {
            index++;
            result[i] = val;
            if (index === promiseArr.length) {
              resolve(result);
            }
          },
          (err) => {
            reject(err);
          }
        );
      });
    });
  }

  static race(promiseArr) {
    return new MyPromise((resolve, reject) => {
      promiseArr.forEach((p, i) => {
        MyPromise.resolve(p).then(
          (val) => {
            resolve(val);
          },
          (err) => {
            reject(err);
          }
        );
      });
    });
  }

  static allSettled(promiseArr) {
    let index = 0;
    const result = [];

    return new MyPromise((resolve, reject) => {
      promiseArr.forEach((p, i) => {
        MyPromise.resolve(p).then(
          (value) => {
            index++;
            result[i] = {
              status: "fulfilled",
              value,
            };
            if (index === promiseArr.length) {
              resolve(result);
            }
          },
          (reason) => {
            index++;
            result[i] = {
              status: "rejected",
              reason,
            };
            if (index === promiseArr.length) {
              resolve(result);
            }
          }
        );
      });
    });
  }

  static any(promiseArr) {
    let time = 0;
    const rejectReasons = [];
    return new MyPromise((resolve, reject) => {
      promiseArr.forEach((p, i) => {
        MyPromise.resolve(p)
          .then((value) => {
            resolve(value);
          })
          .catch((reason) => {
            time++;
            rejectReasons[i] = reason;
            if (time === promiseArr.length) {
              reject(
                new AggregateError(rejectReasons, "All promises were rejected")
              );
            }
          });
      });
    });
  }
}

function resolvePromise(promise, x, resolve, reject) {
  if (promise === x) {
    return reject(
      new TypeError("Chaining cycle detected for promise #<Promise>")
    );
  }
  if (typeof x === "object" || typeof x === "function") {
    if (x === null) {
      return resolve(x);
    }
    let then;
    try {
      then = x.then;
    } catch (err) {
      // 如果取 x.then 的值时抛出错误 error ，则以 error 为据因拒绝 promise
      reject(err);
    }

    if (typeof then === "function") {
      let called = false;
      try {
        then.call(
          x, // this 指向 x
          // 如果 resolvePromise 以值 y 为参数被调用，则运行 [[Resolve]](promise, y)
          (y) => {
            // 如果 resolvePromise 和 rejectPromise 均被调用，
            // 或者被同一参数调用了多次，则优先采用首次调用并忽略剩下的调用
            // 实现这条需要前面加一个变量 called
            if (called) return;
            called = true;
            resolvePromise(promise, y, resolve, reject);
          },
          (r) => {
            if (called) return;
            called = true;
            reject(r);
          }
        );
      } catch (err) {
        if (called) return;
        reject(err);
      }
    } else {
      resolve(x);
    }
  } else {
    resolve(x);
  }
}

MyPromise.deferred = function () {
  const result = {};
  result.promise = new MyPromise((resolve, reject) => {
    result.resolve = resolve;
    result.reject = reject;
  });
  return result;
};

export default MyPromise;
